const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("GameToken", function () {
  async function deployGameTokenFixture() {
    const [owner, addr1, addr2] = await ethers.getSigners();
    const GameToken = await ethers.getContractFactory("GameToken");
    const gameToken = await GameToken.deploy();
    return { gameToken, owner, addr1, addr2 };
  }

  describe("Deployment", function () {
    it("Should set the right owner", async function () {
      const { gameToken, owner } = await loadFixture(deployGameTokenFixture);
      expect(await gameToken.owner()).to.equal(owner.address);
    });

    it("Should have correct name and symbol", async function () {
      const { gameToken } = await loadFixture(deployGameTokenFixture);
      expect(await gameToken.name()).to.equal("Game Token");
      expect(await gameToken.symbol()).to.equal("GTK");
    });
  });

  describe("Minting", function () {
    it("Should allow owner to mint tokens", async function () {
      const { gameToken, owner, addr1 } = await loadFixture(deployGameTokenFixture);
      await gameToken.mint(addr1.address, 1000);
      expect(await gameToken.balanceOf(addr1.address)).to.equal(1000);
    });

    it("Should not allow non-owner to mint tokens", async function () {
      const { gameToken, addr1 } = await loadFixture(deployGameTokenFixture);
      await expect(gameToken.connect(addr1).mint(addr1.address, 1000))
        .to.be.revertedWithCustomError(gameToken, "OwnableUnauthorizedAccount");
    });
  });

  describe("Burning", function () {
    it("Should allow users to burn their own tokens", async function () {
      const { gameToken, owner, addr1 } = await loadFixture(deployGameTokenFixture);
      await gameToken.mint(addr1.address, 1000);
      await gameToken.connect(addr1).burn(500);
      expect(await gameToken.balanceOf(addr1.address)).to.equal(500);
    });

    it("Should allow burning from approved address", async function () {
      const { gameToken, owner, addr1, addr2 } = await loadFixture(deployGameTokenFixture);
      await gameToken.mint(addr1.address, 1000);
      await gameToken.connect(addr1).approve(addr2.address, 500);
      await gameToken.connect(addr2).burnFrom(addr1.address, 500);
      expect(await gameToken.balanceOf(addr1.address)).to.equal(500);
    });
  });

  describe("Transfers", function () {
    it("Should transfer tokens between accounts", async function () {
      const { gameToken, owner, addr1, addr2 } = await loadFixture(deployGameTokenFixture);
      await gameToken.mint(addr1.address, 1000);
      await gameToken.connect(addr1).transfer(addr2.address, 500);
      expect(await gameToken.balanceOf(addr1.address)).to.equal(500);
      expect(await gameToken.balanceOf(addr2.address)).to.equal(500);
    });

    it("Should fail if sender doesn't have enough tokens", async function () {
      const { gameToken, owner, addr1, addr2 } = await loadFixture(deployGameTokenFixture);
      await gameToken.mint(addr1.address, 100);
      await expect(gameToken.connect(addr1).transfer(addr2.address, 200))
        .to.be.revertedWithCustomError(gameToken, "ERC20InsufficientBalance");
    });
  });

  describe("Allowances", function () {
    it("Should update allowance correctly", async function () {
      const { gameToken, owner, addr1, addr2 } = await loadFixture(deployGameTokenFixture);
      await gameToken.mint(addr1.address, 1000);
      await gameToken.connect(addr1).approve(addr2.address, 500);
      expect(await gameToken.allowance(addr1.address, addr2.address)).to.equal(500);
    });

    it("Should transfer from approved amount", async function () {
      const { gameToken, owner, addr1, addr2 } = await loadFixture(deployGameTokenFixture);
      await gameToken.mint(addr1.address, 1000);
      await gameToken.connect(addr1).approve(addr2.address, 500);
      await gameToken.connect(addr2).transferFrom(addr1.address, addr2.address, 500);
      expect(await gameToken.balanceOf(addr2.address)).to.equal(500);
      expect(await gameToken.allowance(addr1.address, addr2.address)).to.equal(0);
    });
  });
});